#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 09-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Drawing;
using System.ComponentModel;

using Thn;
using System.Windows.Forms;
using System.Globalization;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// The third most popular control for getting user inputs.
    /// </summary>
    [ToolboxItem(true), DefaultProperty("Title"), DefaultEvent("NumberValueChanged"), ToolboxBitmap(typeof(ResourceBeacon), "Thn.Interface.Vcl.Resources.Icons.NumberBox.bmp")]
#if DEBUG
    public
#else
    public
#endif
 class NumberBox : EditBox
    {
        #region Events

        #region Number Value Changed
        /// <summary>
        /// This event is raised after property Value of control is changed.
        /// </summary>
        public event NumberValueChangedEventHandler NumberValueChanged;

        /// <summary>
        /// Invoke event ValueChanged.
        /// </summary>
        protected void RaiseNumberValueChanged(decimal oldValue, decimal newValue)
        {
            NumberValueChangedEventHandler handler = NumberValueChanged;
            if (handler != null) handler(this, oldValue, newValue);
            #region Defensive tracing
            else
            {
                // The control event handler is not register, do nothing.
            }
            #endregion
        }
        #endregion

        #endregion

        #region Fields
        /// <summary>
        /// Result of lasted view.
        /// </summary>
        string mLastPreview = string.Empty;
        /// <summary>
        /// Projected start index
        /// </summary>
        int mProjectedIndex = -1;
        /// <summary>
        /// Index projection mode
        /// </summary>
        bool mIsProjecting = false;
        /// <summary>
        /// When true, the next format will have "." sign
        /// </summary>
        bool mIsAppendDecimalSign = false;
        /// <summary>
        /// When true, the next format will include a zero
        /// </summary>
        bool mIsAppendZero = false;
        /// <summary>
        /// When true, Value Changed Event will be forced to raised
        /// </summary>
        bool mForceRaiseValueChangedEvent = false;
        /// <summary>
        /// Number of zeros to append
        /// </summary>
        int mZeroCount = 0;
        #endregion

        #region Properties

        #region Values

        #region Value
        private object mValue = 0;
        /// <summary>
        /// Gets/Sets current value.
        /// </summary>
        [Browsable(true), DefaultValue(0), Category("Value"), Description("Current value")]
        public object Value
        {
            get { return mValue; }
            set
            {
                if (value != null && mValue != value)
                {
                    // backup old value
                    object oldValue = mValue;

                    OnSetValue(value);

                    // raise event.
                    if (mValue != oldValue)
                    {
                        OnValueChanged(oldValue, mValue);
                        //RaiseNumberValueChanged(oldValue, mValue);
                        RaiseValueChanged(EventArgs.Empty);
                    }
                    #region Defensive tracing
                    else
                    {
                        // the not raise event with the same value.
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    // do not set null or the same value.
                }
                #endregion
            }
        }
        #endregion

        #region Min Value
        private decimal mMinValue = 0;
        /// <summary>
        /// Gets/Sets minimum value.
        /// </summary>
        [Browsable(true), DefaultValue(0), Category("Value"), Description("Miminum value")]
        public decimal MinValue
        {
            get { return mMinValue; }
            set
            {
                mMinValue = value;
                if (mMinValue != 0 && mMinValue >= mMaxValue) mMinValue = mMaxValue - 1;
                #region Defensive tracing
                else
                {
                    //valid case
                }
                #endregion

                #region Check limitation
                decimal currentValue = Convert.ToDecimal(mValue);
                if (currentValue < mMinValue)
                {
                    switch (mNumberType)
                    {
                        case NumberType.Natural:
                        case NumberType.Integer:
                            mValue = Convert.ToInt32(mMinValue);
                            break;
                        case NumberType.Decimal:
                        case NumberType.NonNegativeDecimal:
                            mValue = mMinValue;
                            break;
                        #region Defensive tracing
                        default:
                            throw new NotSupportedException(string.Format("Do not support this type {0}.", mNumberType));
                            break;
                        #endregion
                    }
                }
                #region Defensive tracing
                else
                {
                    // valid value, no change.
                }
                #endregion
                #endregion
            }
        }
        #endregion

        #region Max Value
        private decimal mMaxValue = 0;
        /// <summary>
        /// Gets/Sets maximum value.
        /// </summary>
        [Browsable(true), DefaultValue(0), Category("Value"), Description("Maximum value")]
        public decimal MaxValue
        {
            get { return mMaxValue; }
            set
            {
                mMaxValue = value;

                if (mMaxValue != 0 && mMaxValue <= mMinValue) mMaxValue = mMinValue + 1;
                #region Defensive tracing
                else
                {
                    //valid, move on
                }
                #endregion

                #region Check limitation
                decimal currentValue = Convert.ToDecimal(mValue);
                if (currentValue > mMaxValue)
                {
                    switch (mNumberType)
                    {
                        case NumberType.Natural:
                        case NumberType.Integer:
                            mValue = Convert.ToInt32(mMaxValue);
                            break;
                        case NumberType.Decimal:
                        case NumberType.NonNegativeDecimal:
                            mValue = mMaxValue;
                            break;
                        #region Defensive tracing
                        default:
                            throw new NotSupportedException(string.Format("Do not support this type {0}.", mNumberType));
                            break;
                        #endregion
                    }
                }
                #region Defensive tracing
                else
                {
                    // valid value, no change.
                }
                #endregion
                #endregion
            }
        }
        #endregion

        #region Number Type
        private NumberType mNumberType = NumberType.Integer;
        /// <summary>
        /// Gets/Sets the type of number this editor can accept.
        /// </summary>
        [Browsable(true), DefaultValue(NumberType.Integer), Category("Value"), Description("Gets or sets numeric type before input.")]
        public NumberType NumberType
        {
            get { return mNumberType; }
            set { mNumberType = value; }
        }
        #endregion

        #region Value As Int
        /// <summary>
        /// Gets value as integer number.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int ValueAsInt
        {
            get { return Convert.ToInt32(mValue); }
        }
        #endregion

        #region Value As Double
        /// <summary>
        /// Gets value as double number.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Double ValueAsDouble
        {
            get { return Convert.ToDouble(mValue); }

        }
        #endregion

        #region Value As Decimal
        /// <summary>
        /// Gets value as decimal number.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public decimal ValueAsDecimal
        {
            get { return Convert.ToDecimal(mValue); }
        }
        #endregion

        #endregion

        #region Visuals

        #region Format
        private string mFormat = "";
        /// <summary>
        /// Gets or Sets value of Format
        /// </summary>
        [Browsable(true), DefaultValue(""), Category("Appearance"), Description("Input number format.")]
        public string Format
        {
            get { return mFormat; }
            set { mFormat = value; }
        }
        #endregion

        #region Alignment
        /// <summary>
        /// Gets or Sets alignment of content.
        /// </summary>
        [Browsable(true), DefaultValue(ContentAlignment.MiddleRight), Category("Appearance"), Description("How to arrange contents")]
        public ContentAlignment Alignment
        {
            get { return RenderContext.Alignment; }
            set
            {
                RenderContext.Alignment = value;
                InvalidateLayout();
            }
        }
        #endregion

        #region Text Style
        /// <summary>
        /// Gets/Sets font style for rendering text.
        /// </summary>
        [Browsable(true), DefaultValue(TextStyle.Normal), Category("Appearance"), Description("Font style for rendering text")]
        public TextStyle TextStyle
        {
            get { return RenderContext.TextStyle; }
            set
            {
                RenderContext.TextStyle = value;
                Invalidate();
            }
        }
        #endregion

        #region Render Context
        /// <summary>
        /// Gets current render context
        /// </summary>
        protected new NumberBoxRenderContext RenderContext
        {
            get { return ((NumberBoxRenderContext)base.RenderContext); }
        }
        #endregion

        #region Text
        /// <summary>
        /// DO NOT SET ANY VALUE TO THIS PROPERTY.
        /// <para>This property is created from base class of the number box control.</para>
        /// <para>The number box does not support this property.</para>
        /// </summary>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override string Text
        {
            get { return Inner.Text; }
            // do not set any value here.
            set { }
        }
        #endregion

        #region Show Drop Button
        /// <summary>
        /// Gets or Sets value indicate that whether the drop button will be showed or not.
        /// </summary>
        [Browsable(true), DefaultValue(false), Category("Appearance"), Description("Gets or Sets value indicate that whether the drop button will be showed or not.")]
        public bool ShowDropButton
        {
            get { return mShowDropButton; }
            set
            {
                if (mShowDropButton != value)
                {
                    mShowDropButton = value;
                    if (!DesignMode)
                    {
                        InvalidateLayout();
                    }
                    #region Defensive tracing
                    else
                    {
                        // do not render in runtime mode.
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    // do not set the same value.
                }
                #endregion
            }
        }
        #endregion

        #endregion

        #endregion

        #region Base methods

        #region On Set Value
        /// <summary>
        /// This method will be called after end user set value to Value property of control.
        /// </summary>
        protected virtual void OnSetValue(object value)
        {
            decimal newValue = Convert.ToDecimal(value);

            #region Check Limitation
            if (mMinValue != mMaxValue)
            {
                if (newValue < mMinValue)
                {
                    newValue = mMinValue;
                }
                #region Defensive tracing
                else
                {
                    // valid value, go on.
                }
                #endregion

                if (newValue > MaxValue)
                {
                    newValue = mMaxValue;
                }
                #region Defensive tracing
                else
                {
                    // valid value, go on.
                }
                #endregion
            }
            #region Defensive tracing
            else
            {
                // do not process limit value in the case of Mix value equal max value and them both equal zero.
            }
            #endregion
            #endregion
            // set new value to current value.
            mValue = newValue;

            bool isFirstNegativeSign = false;
            string seperator = UI.Culture.NumberFormat.CurrencyDecimalSeparator;
            string negativeSign = UI.Culture.NumberFormat.NegativeSign;

            decimal d = 0;
            if ((value is decimal) || (value is int) || (value is double) || (value is long))
            {
                if (value is decimal) d = (decimal)value;
                else if (value is int) d = (decimal)(int)value;
                else if (value is double) d = (decimal)(double)value;
                else if (value is long) d = (decimal)(long)value;
            }
            else
            {
                string s = value.ToString();
                s = CleanText(s);
                if (string.IsNullOrEmpty(s)) d = 0;
                else if
                    (s == negativeSign || // "-"
                    s == string.Format("{0}0", negativeSign) || // "-0"
                    s == string.Format("0{0}", negativeSign)) //"0-"
                {
                    d = 0;
                    SetInnerText(negativeSign);
                    isFirstNegativeSign = true;
                }
                else decimal.TryParse(s, NumberStyles.Any, UI.Culture, out d);
            }

            d = OnNormalizeValue(d);
            if (!isFirstNegativeSign) SetInnerText(FormatNumber(d));
            if (string.IsNullOrEmpty(Text))
            {
                SetInnerText("0");
            }
            #region Defensive tracing
            else
            {
                // valid value, keep current value.
            }
            #endregion
            mValue = d;

            if (mIsAppendDecimalSign)
            {
                SetInnerText(Text + seperator);
                mIsAppendDecimalSign = false;
            }

            if (mIsAppendZero)
            {
                int idx = Text.IndexOf(seperator);
                if (idx < 0)
                {
                    SetInnerText(Text + seperator);
                }
                SetInnerText(Text + GetZeros(mZeroCount));
                mIsAppendZero = false;
            }

            SetInnerText(Text);
            Invalidate();
        }
        #endregion

        #region Normalize Value
        /// <summary>
        /// Ensure that a value conforms to current constraint
        /// </summary>
        protected virtual decimal OnNormalizeValue(decimal value)
        {
            decimal result = value;

            if (mNumberType == NumberType.Integer) result = decimal.Truncate(result);
            else if (mNumberType == NumberType.Natural)
            {
                result = decimal.Truncate(result);
                if (result < 0) result = 0;
            }
            else if (mNumberType == NumberType.NonNegativeDecimal)
            {
                if (result < 0) result = 0;
            }

            return result;
        }
        #endregion

        #region On Create Render Context
        /// <summary>
        /// Create a new render context instance.
        /// </summary>
        protected override EditBoxRenderContext OnCreateRenderContext()
        {
            NumberBoxRenderContext result = new NumberBoxRenderContext();
            result.Alignment = ContentAlignment.MiddleRight;
            result.Multiline = false;
            result.TextStyle = TextStyle.Normal;

            return result;
        }
        #endregion

        #region On Value Changed
        /// <summary>
        /// This method will be called after value of control is changed.
        /// </summary>
        /// <param name="oldValue">Value before changed.</param>
        /// <param name="newValue">Value after changed.</param>
        protected virtual void OnValueChanged(object oldValue, object newValue) { }
        #endregion

        #region Notify On Lost Focus
        /// <summary>
        /// Override this method for validate the limit value in the last time
        /// when control is lost focus.
        /// </summary>
        internal override void NotifyInnerTextBoxOnLostFocus(EventArgs e)
        {
            base.NotifyInnerTextBoxOnLostFocus(e);

            ValidateLimit();
        }
        #endregion

        #region Notify On Text Changed
        /// <summary>
        /// Override this method for check whether need raise value changed event or not
        /// when there are any new value set to inner Text property.
        /// </summary>
        internal override void NotifyInnerTextBoxOnTextChanged()
        {
            base.NotifyInnerTextBoxOnTextChanged();

            if (mIsProjecting)
            {
                mIsProjecting = false;//suppress projection feedback
                //this.Value = CleanText(Inner.Text);
                Value = CleanText(Inner.Text);
                this.SelectionStart = FindProjectedIndex();
            }
            #region Defensive tracing
            else
            {
                // do not need to update value in this case.
            }
            #endregion

            //force raising event
            if (mForceRaiseValueChangedEvent)
            {
                mForceRaiseValueChangedEvent = false;
                OnValueChanged(0, mValue);
                //RaiseNumberValueChanged(0, mValue);
                RaiseValueChanged(EventArgs.Empty);
            }
            #region Defensive tracing
            else
            {
                // just raise event when this flag is turn on.
            }
            #endregion
        }
        #endregion

        #region Create Inner Text Box
        /// <summary>
        /// Override this method for prevent user used context menu or paste value 
        /// from clipboad to control.
        /// </summary>
        protected override NativeWindowsTextBox CreateInnerTextBox()
        {
            NativeWindowsTextBox result = base.CreateInnerTextBox();
            result.AllowContextMenu = false;

            return result;
        }
        #endregion

        #endregion

        #region Key events processing
        /// <summary>
        /// over This method for process the number value from user input key.
        /// </summary>
        internal override void NotifyInnerTextBoxOnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
        {
            base.NotifyInnerTextBoxOnKeyPress(e);
            bool allow = false;
            char c = e.KeyChar;

            string negativeSign = UI.Culture.NumberFormat.NegativeSign;
            string groupSeperator = UI.Culture.NumberFormat.CurrencyGroupSeparator;
            string decimalSeparator = UI.Culture.NumberFormat.CurrencyDecimalSeparator;
            allow = CheckValidKey(c);

            if (allow)
            {
                mLastPreview = PreviewResultText(c.ToString());

                if (!Char.IsDigit(c))
                {//non-digit character

                    #region Non-digit character

                    if ((Keys)e.KeyChar == Keys.Back)
                    {
                        #region Back

                        allow = true;
                        mIsProjecting = true;
                        mProjectedIndex = Inner.SelectionStart - 1;
                        mProjectedIndex = FindCleanedIndex(mLastPreview, mProjectedIndex);

                        #endregion
                    }
                    else if (c.ToString() == negativeSign)
                    {
                        #region Negative sign

                        allow = CanAcceptMinus();
                        //mProjecting = true;

                        if ((mNumberType != NumberType.NonNegativeDecimal) &&
                            (mNumberType != NumberType.Natural))
                        {
                            mIsProjecting = true;

                            if (Inner.SelectionStart == 0 && Inner.SelectionLength > 0)
                            {
                                allow = true;
                            }
                            else
                            {
                                if (Text.Contains(negativeSign))
                                {
                                    mProjectedIndex--;
                                }
                                else
                                {
                                    mProjectedIndex++;
                                }
                                Value = -this.ValueAsDecimal;
                            }
                        }
                        #region Defensive tracing
                        else
                        {
                            // this type of number do not support negative sign.
                        }
                        #endregion

                        #endregion
                    }
                    else if (c.ToString() == groupSeperator)
                    {
                        #region Group Separator
                        
#if DEBUG
                        Log.Information("Group separator");
#endif
                        allow = false;
                        #endregion
                    }
                    else if (c.ToString() == decimalSeparator)
                    {
#if DEBUG
                        Log.Warning("Decimal separator");
#endif
                        #region Decimal Separator

                        allow = CanAcceptDecimalSeparator();

                        if (allow)
                        {
                            mIsProjecting = true;
                            mProjectedIndex = Inner.SelectionStart + 1;
                            mProjectedIndex = FindCleanedIndex(mLastPreview, mProjectedIndex);

                            if (Inner.SelectionStart >= Inner.Text.Length) mIsAppendDecimalSign = true;
                        }

                        #endregion
                    }
                    else allow = false;

                    if (allow) mForceRaiseValueChangedEvent = true;

                    #endregion
                }
                else
                {//digit character

                    #region Digit character

                    mIsProjecting = true;
                    mProjectedIndex = Inner.SelectionStart + 1;
                    mProjectedIndex = FindCleanedIndex(mLastPreview, mProjectedIndex);

                    #region Check out of range
                    decimal decimalScope = 0;
                    int intScope = 0;
                    string txt = "";
                    switch (NumberType)
                    {
                        case NumberType.Natural:
                        case NumberType.Integer:
                            txt = CleanText(mLastPreview);
                            //allow = int.TryParse(txt, out intScope);
                            allow = decimal.TryParse(txt, out decimalScope);
                            intScope = (int)decimalScope;
                            break;
                        case NumberType.Decimal:
                        case NumberType.NonNegativeDecimal:
                            txt = CleanText(mLastPreview);
                            allow = decimal.TryParse(txt, out decimalScope);
                            break;
                        #region Defensive tracing
                        default:
                            throw new NotSupportedException(string.Format("Do not support this type of number {0}", NumberType));
                        #endregion
                    }
                    #endregion

                    if (allow)
                    {
                        #region 0
                        //to solve .0 problem
                        if ((c == '0') && (mLastPreview == "0"))
                        {
                            if (Inner.SelectionStart >= Inner.Text.Length && Inner.Text.Length > 0)
                            {
                                c = Inner.Text[Inner.SelectionStart - 1];
                                //try to solve .0 problem
                                int idx = Inner.Text.IndexOf(groupSeperator);
                                if ((Inner.Text.Length > 1) && (Inner.Text[Inner.SelectionStart - 1] == '0'))
                                {//solve .00 problem
                                    mIsAppendZero = true;

                                    //calculate the number of zeros to append
                                    if (idx > -1)
                                    {
                                        mZeroCount = FindZeroCount(Inner.Text, Inner.SelectionStart - 1) + 1;
                                    }
                                    else mZeroCount = 1;
                                }
                                else if (idx < Inner.SelectionStart)
                                {//solve .0 problem
                                    mIsAppendZero = true;
                                    mZeroCount = 1;
                                }
                            }
                        }
                        #endregion

                        #region Validate min/max value
                        if (mMinValue < mMaxValue)
                        {
                            string newValue = Inner.Text == null ? string.Empty : Inner.Text;
                            newValue = newValue.Insert(Inner.SelectionStart, c.ToString());
                            int minValueLength = Math.Abs(mMinValue).ToString().Length;
                            if (newValue.Length >= minValueLength)
                            {
                                decimal value = 0;

                                decimal.TryParse(newValue, out value);
                                allow = value >= mMinValue && value <= mMaxValue;
                            }
                            #region Defensive Tracing
                            // The input value is too small cannot be deny input here
                            #endregion

                            allow = Inner.SelectedText == Inner.Text ? true : allow;
                        }
                        #region Defensive Tracing
                        // Do not validate min/max in case of the min value equal max value.
                        #endregion
                        #endregion
                    }
                    #region Defensive tracing
                    else
                    {
                        // out of range, not allow input this key
                    }
                    #endregion

                    #endregion
                }
            }
            #region Defensive tracing
            else
            {
                // invalid key.
            }
            #endregion

            e.Handled = !allow;

#if DEBUG
            Log.Debug("NotifyKeyPress - key: {0} allow: {1}", c, allow);
#endif
        }
        /// <summary>
        /// Override this method for process when user click on the delete key.
        /// </summary>
        internal override bool NotifyProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (Text.Length > 0)
            {
                if (keyData == Keys.Delete)
                {
                    mIsProjecting = true;
                    mProjectedIndex = Inner.SelectionStart;
                    if (Text.Contains(UI.Culture.NumberFormat.NumberGroupSeparator))
                    {
                        mProjectedIndex--;
                    }
                    #region Defensive tracing
                    else
                    {
                        // valid index
                    }
                    #endregion
                }
                else if (keyData == Keys.Left)
                {
                    mProjectedIndex--;
                }
                else if (keyData == Keys.Right)
                {
                    mProjectedIndex++;
                }

                if (mProjectedIndex < 0)
                {
                    mProjectedIndex = 0;
                }
                else if (mProjectedIndex >= Text.Length)
                {
                    mProjectedIndex = Text.Length;
                }
                #region Defensive tracing
                else
                {
                    // valid value
                }
                #endregion
            }
            #region Defensive tracing
            else
            {
                // empty text, do not need to process
            }
            #endregion

            return base.NotifyProcessCmdKey(ref msg, keyData);
        }
        #endregion

        #region Inner methods

        #region Validate Limit
        /// <summary>
        /// Check whether the input value is less than or equal max value
        /// and greater than or equal min value or not.
        /// </summary>
        void ValidateLimit()
        {
            if (mMinValue < mMaxValue)
            {
                if (ValueAsDecimal < mMinValue)
                {
                    mValue = mMinValue;
                    SetInnerText(mMinValue.ToString());
                }
                else if (ValueAsDecimal > mMaxValue)
                {
                    mValue = mMaxValue;
                    SetInnerText(mMaxValue.ToString());
                }
                #region Defensive tracing
                else
                {
                    // initialize value if so good, not need to changed anything.
                }
                #endregion
            }
            #region Defensive Tracing
            else
            {
                // Do not validate min/max in case of the min value equal max value.
            }
            #endregion
        }
        #endregion

        #region Find Projected Index
        /// <summary>
        /// Find the actual position the caret should be after entering new digit
        /// </summary>
        /// <returns></returns>
        int FindProjectedIndex()
        {
            bool keepSearching = true;
            int orgIndex = 0;
            int actualIndex = 0;
            string source = Inner.Text;

            string seperator = UI.Culture.NumberFormat.CurrencyDecimalSeparator;
            string negativeSign = UI.Culture.NumberFormat.NegativeSign;

            while (keepSearching && actualIndex < source.Length)
            {
                Char c = source[actualIndex];
                if ((Char.IsDigit(c)) || (c.ToString() == seperator) || (c.ToString() == negativeSign))
                {
                    orgIndex++;
                }
                #region Defensive tracing
                else
                {
                    // Miss match, go next.
                }
                #endregion

                //next iteration
                actualIndex++;
                if (orgIndex >= mProjectedIndex) keepSearching = false;
                if (actualIndex >= source.Length) keepSearching = false;
            }

            return actualIndex;
        }
        #endregion

        #region Find Cleaned Index
        /// <summary>
        /// Find the absolute index after cleaning the source text
        /// </summary>
        int FindCleanedIndex(string source, int orgIdx)
        {
            int result = 0;
            bool keepSearching = true;
            int idx = 0;

            string seperator = UI.Culture.NumberFormat.CurrencyDecimalSeparator;
            string negativeSign = UI.Culture.NumberFormat.NegativeSign;

            while (keepSearching)
            {
                Char c = source[idx];
                if ((Char.IsDigit(c)) || (c.ToString() == seperator) || (c.ToString() == negativeSign))
                {
                    result++;
                }
                #region Defensive tracing
                else
                {
                    // Miss match, go next.
                }
                #endregion

                //next iteration
                idx++;
                if (idx >= orgIdx) keepSearching = false;
                #region Defensive tracing
                else
                {
                    // search available, go next.
                }
                #endregion

                if (idx >= source.Length) keepSearching = false;
                #region Defensive tracing
                else
                {
                    // search available, go next.
                }
                #endregion
            }

            return result;
        }
        #endregion

        #region Format Number
        /// <summary>
        /// Format a value
        /// </summary>
        string FormatNumber(decimal value)
        {
            string result = "";

            //determine the precision degree
            int i = 0;
            bool next = true;

            string format = mFormat;
            if ((format == null) || (format == ""))
            {//no format specified, try to guess the best format

                format = "#,#.";
                decimal d1 = Math.Abs(value);
                while (next)
                {
                    decimal remainder = d1 - decimal.Truncate(d1);
                    if (remainder > 0)
                    {
                        d1 = d1 * 10;
                        format += "0";
                    }

                    i++;
                    if (i > 7) next = false;
                }
            }

            result = value.ToString(format, UI.Culture);

            if ((value > 0) && (value < 1.0m)) result = "0" + result;
            else if ((value < 0) && (value > -1.0m)) result = "-" + FormatNumber(Math.Abs(value));

            return result;
        }
        #endregion

        #region Can Accept Minus
        /// <summary>
        /// Checks to see if Minus character is allowed
        /// </summary>
        bool CanAcceptMinus()
        {
            bool result = false;

            if ((mNumberType == NumberType.Integer) ||
                (mNumberType == NumberType.Decimal))
            {
                if (Inner.SelectionStart == 0)
                {//at the very first location
                    if (Inner.Text.Length == 0) result = true;//no text
                    else if (Inner.Text[0] != '-') result = true;//first character is not yet a negative sign
                }
            }

            return result;
        }
        #endregion

        #region Can Accept Decimal Separator
        /// <summary>
        /// Checks to see if Decimal Separator character is allowed
        /// </summary>
        /// <returns></returns>
        bool CanAcceptDecimalSeparator()
        {
            bool result = false;

            if ((mNumberType == NumberType.Decimal) || (mNumberType == NumberType.NonNegativeDecimal))
            {
                string separator = UI.Culture.NumberFormat.CurrencyDecimalSeparator;
                string preview = PreviewResultText(separator);
#if DEBUG
                Log.Warning(preview);
#endif
                int firstIdx = preview.IndexOf(UI.Culture.NumberFormat.CurrencyDecimalSeparator);
                bool proceed = true;
                if (firstIdx < 0) proceed = true;
                else
                {
                    //already has a separator? check again
                    int secondIdx = preview.IndexOf(separator, firstIdx + 1);
                    if (secondIdx > firstIdx)
                    {
                        //has more than one separator -> reject
                        proceed = false;

#if DEBUG
                        Log.Information("more than one separator");
#endif
                    }
                    else proceed = true;

#if DEBUG
                    Log.Information("First: {0} Second: {1}", firstIdx, secondIdx);
#endif
                }

                if (proceed)
                {//no separator exists, proceed
                    result = CheckValidText(preview);
                }
            }
            #region Defensive tracing
            else
            {
                //
            }
            #endregion

            return result;
        }
        #endregion

        #region Preview Result Text
        /// <summary>
        /// Build a text to see what happen if we accept the provided character
        /// </summary>
        string PreviewResultText(string c)
        {
            string result = string.Empty;
            int start = Inner.SelectionStart;
            int count = Inner.SelectionLength;
            string txt = Inner.Text;

            //copy from left of selection
            result = txt.Substring(0, start);

            //append the character itself
            result += c;

            //copy from right of selection
            start += count;
            result += txt.Substring(start, txt.Length - start);

            //store for later projection
            mLastPreview = result;

            return result;
        }
        #endregion

        #region Find Zero Count
        /// <summary>
        /// Count the number of consecutive zeros from the right
        /// </summary>
        int FindZeroCount(string s, int fromIdx)
        {
            int result = 0;
            bool keepCounting = true;
            while (keepCounting)
            {
                if (s[fromIdx] != '0') keepCounting = false;
                else result++;

                //next iteration
                fromIdx--;
                if (fromIdx < 0) keepCounting = false;
            }

            return result;
        }
        #endregion

        #region Get Zeros
        /// <summary>
        /// Gets a zero string
        /// </summary>
        string GetZeros(int count)
        {
            string result = "";
            for (int i = 0; i < count; i++)
            {
                result += '0';
            }

            return result;
        }
        #endregion

        #region Clean Text
        /// <summary>
        /// Remove non-necessary characters from a text
        /// </summary>
        string CleanText(string value)
        {
            string result = string.Empty;
            bool hasMinus = false;
            bool hasDecimal = false;
            Char c;
            string decimalSeparator = UI.Culture.NumberFormat.CurrencyDecimalSeparator;

            for (int i = 0; i < value.Length; i++)
            {
                c = value[i];
                if (Char.IsDigit(c))
                {
                    result += c;
                    hasMinus = true;//do not allow any minus sign after a digit
                }
                else if (c.ToString() == decimalSeparator)
                {
                    if (!hasDecimal)
                    {
                        result += c;
                        hasDecimal = true;
                    }
                    hasMinus = true;//do not allow any minus sign after a decimal
                }
                else if (c == '-')
                {
                    if (!hasMinus)
                    {
                        result += c;
                        hasMinus = true;
                    }
                }
            }

            return string.IsNullOrEmpty(result) ? "0" : result;
        }
        #endregion

        #region Check Valid Text
        /// <summary>
        /// Checks to see if a string is a valid number text
        /// </summary>
        bool CheckValidText(string value)
        {
            value = CleanText(value);

            decimal tmp;
            return decimal.TryParse(value, out tmp);
        }
        #endregion

        #region Check Valid Key
        /// <summary>
        /// Check whether a specific input key data is a valid key or not,
        /// a valid key is the key including a set of key [0..9, -,[numeric seperator].
        /// </summary>
        bool CheckValidKey(char keyData)
        {
            bool result = false;

            char[] samples = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', ',', '\b' };
            foreach (char c in samples)
            {
                result = keyData == c;
                if (result) break;
                #region Defensive tracing
                else
                {
                    // miss match, go next.
                }
                #endregion
            }

            return result;
        }
        #endregion

        #region Set Inner Text
        /// <summary>
        /// Set inner text without raise text changed event.
        /// </summary>
        void SetInnerText(string value)
        {
            mSuspendTextChangedEvent = true;
            Inner.Text = value;
            mSuspendTextChangedEvent = false;
        }
        #endregion

        #endregion

        #region Public methods

        #region Set Value
        /// <summary>
        /// Sets value to control without raise event.
        /// </summary>
        public void SetValue(object value)
        {
            mValue = value;

            Invalidate();
        }
        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public NumberBox()
        {
            this.Alignment = ContentAlignment.MiddleRight;
        }
        #endregion
    }

    /// <summary>
    /// This event will be raised when the value of number box control is changed.
    /// </summary>
    /// <param name="sender">The number box control.</param>
    /// <param name="oldValue">Value before changed.</param>
    /// <param name="newValue">Value after changed.</param>
    public delegate void NumberValueChangedEventHandler(object sender, decimal oldValue, decimal newValue);
}